/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simulation;

import java.util.List;
import logging.LogSetup;
import org.apache.log4j.Logger;
import org.gnu.glpk.GLPK;
import org.gnu.glpk.GLPKConstants;
import org.gnu.glpk.SWIGTYPE_p_double;
import org.gnu.glpk.SWIGTYPE_p_int;
import org.gnu.glpk.glp_prob;
import org.gnu.glpk.glp_smcp;

/**
 *
 * @author Danielondon
 */
public class GLPKSolver implements ISolver{
    protected Logger logger = LogSetup.getLogger();
    int slotCount;
    double []prices;
    int [] connected;
    double [] requiredConsumption;
    //double minCharge;
    double maxCharge;
    List<int[]> patterns;
    private String name;
            
    public GLPKSolver(String name, int slotCount, double []prices, int [] connected, 
            List<int[]> patterns, double[] requiredConsumption, double maxCharge) throws Exception{
        this.name = name;
        this.slotCount = slotCount;
        this.prices = prices; 
        this.connected = connected;
        this.requiredConsumption = requiredConsumption;
        //this.minCharge =minCharge;
        this.maxCharge = maxCharge; 
        this.patterns = patterns;
        
        if (prices.length != slotCount || connected.length != slotCount) {
            throw new Exception ("Invalid dimentsion of prices or connected arrays.");
        }
        if (patterns.size() != requiredConsumption.length) {
            throw new Exception ("Invalid dimension of required consumption or driving patterns.");
        }
    }
    
    @Override
    public double[] solve(){
        // Variables declaration
        double[] result = null;
        glp_prob lp;
        glp_smcp parm;
        SWIGTYPE_p_int indI, indJ;
        SWIGTYPE_p_double val;
        int ret;
        
        // Problem creation
        lp = GLPK.glp_create_prob();
        GLPK.glp_set_prob_name(lp, name);
        logger.info("Problem created " + GLPK.glp_get_prob_name(lp));
        
        // Variables Definiton
        GLPK.glp_add_cols(lp, this.slotCount);
        
        for (int i =1; i <= slotCount; i++){
            GLPK.glp_set_col_name(lp, i, "x"+i);
            GLPK.glp_set_col_kind(lp, i, GLPKConstants.GLP_CV);
            // Constraint Limits
            // Between 0 and the max charge rate per slot as long as it is connected
            if (this.connected[i-1] == CommonResources.CONNECTED)
                GLPK.glp_set_col_bnds(lp, i, GLPKConstants.GLP_DB, 0, this.maxCharge);
            else
                GLPK.glp_set_col_bnds(lp, i, GLPKConstants.GLP_FX, 0, 0);
            // The coefficients are the prices times if in that slot is connected or not
            GLPK.glp_set_obj_coef(lp, i, this.prices[i-1]*this.connected[i-1] );
        }
        
        GLPK.glp_add_rows(lp, this.patterns.size());
        
        for (int i =1 ; i <= this.patterns.size(); i++){
            int [] pattern = this.patterns.get(i-1);
            GLPK.glp_set_row_name(lp, i, "c"+i);
            // It has to be equal to the consumption 
            GLPK.glp_set_row_bnds(lp, i, GLPKConstants.GLP_FX, this.requiredConsumption[i-1], this.requiredConsumption[i-1]);
        }
        
        // INDEXES FOR THE CONSTRAINTS
        // The int arrays conform the coordinates i and j of the matrix
        
        // Size of the slots times the size of the consumption constraings
        int matrixSize = this.slotCount*this.requiredConsumption.length;
        indI = GLPK.new_intArray(matrixSize);
        int value = 1;
        for (int i = 1; i <= matrixSize; i++ ){
            if ((i-1) % slotCount == 0 && i!= 1 )
                value ++;
            //logger.info(String.format("IndI %s - %s ", i, value));
            GLPK.intArray_setitem(indI, i, value);        
        }

        indJ = GLPK.new_intArray(matrixSize);
        for (int i = 1; i <= matrixSize; i++ ){
            value = (i % slotCount == 0)? slotCount : (i % slotCount);
            //logger.info(String.format("IndI %s - %s ", i, value));
            GLPK.intArray_setitem(indJ, i, value);        
        }
        
        // Getting i and j coordinate, and then set the proper value of the requiemetn
        val = GLPK.new_doubleArray(matrixSize);
        int i = 1;
        double requirement;
        for (int n = 1; n <= matrixSize; n++ ){
            if ((n-1) % slotCount == 0 && n != 1)
                i++;
            int j = (n % slotCount == 0)? slotCount : (n % slotCount);
            requirement = this.patterns.get(i-1)[j-1];
            //logger.info(String.format("Setting (%s,%s) = %s ", i, j, requirement));                    
            GLPK.doubleArray_setitem(val, n, requirement);            
        }

        GLPK.glp_load_matrix(lp, matrixSize, indI, indJ, val);
        
        // Define objective
        GLPK.glp_set_obj_name(lp, "z");
        GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MIN);
    
        // Solve model
        parm = new glp_smcp();
        GLPK.glp_init_smcp(parm);
        ret = GLPK.glp_simplex(lp, parm);

        // Retrieve solution
        if (ret == 0) {
            //write_lp_solution(lp);
            result = get_lp_solution(lp);            
        } else {
            logger.warn("The problem could not be solved");
        }

        // Free memory
        GLPK.glp_delete_prob(lp);
        
        return result;
    }
    
    private double[] get_lp_solution(glp_prob lp) {
        int i;
        int n;
        String name;
        double val;
        double [] solution = new double[slotCount];
        name = GLPK.glp_get_obj_name(lp);
        val = GLPK.glp_get_obj_val(lp);
        //logger.info(name + " = " +val);
        n = GLPK.glp_get_num_cols(lp);
        for (i = 1; i <= n; i++) {
            name = GLPK.glp_get_col_name(lp, i);
            val = GLPK.glp_get_col_prim(lp, i);
            solution[i-1] = val;
            //logger.info(name + " = " + val);
        }
        
        return solution;
    }
}
